package com.bittech.servlet.smallfileserver.dao.impl;

import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.OSSObject;
import com.bittech.servlet.smallfileserver.common.Page;
import com.bittech.servlet.smallfileserver.dao.SmallFileDao;
import com.bittech.servlet.smallfileserver.domain.SmallFile;
import com.bittech.servlet.smallfileserver.web.config.listener.DataSourceLoaderListener;
import com.bittech.servlet.smallfileserver.web.config.listener.OssClientLoaderListener;
import org.bson.types.Binary;

import javax.sql.DataSource;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;

/**
 * Author: secondriver
 * Created: 2018/9/20
 */
public class OssSmallDaoImpl implements SmallFileDao {
    
    // Bucket用来管理所存储Object的存储空间
    private static String bucketName = "small-file-save";
    
    private OSSClient ossClient = OssClientLoaderListener.ossClient;
    
    private DataSource dataSource = DataSourceLoaderListener.dataSource;
    
    @Override
    public void save(SmallFile smallFile) {
        String sql = "insert into file_info (id, name, content_type, size, upload_date, md5) values (?,?,?,?,?,?);";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)
        ) {
            statement.setString(1, smallFile.getId());
            statement.setString(2, smallFile.getName());
            statement.setString(3, smallFile.getContentType());
            statement.setLong(4, smallFile.getSize());
            statement.setTimestamp(5, Timestamp.valueOf(smallFile.getUploadDate()));
            statement.setString(6, smallFile.getMd5());
            int effect = statement.executeUpdate();
            if (effect == 1) {
                ossClient.putObject(bucketName, smallFile.getId(), new ByteArrayInputStream(smallFile.getContent().getData()));
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public void delete(String id) {
        String sql = "delete from file_info where id=?;";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)
        ) {
            statement.setString(1, id);
            int effect = statement.executeUpdate();
            if (effect == 1) {
                ossClient.deleteObject(bucketName, id);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
    
    @Override
    public SmallFile find(String id) {
        String sql = "select id, name, content_type, size, upload_date, md5 from file_info where id=? ;";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)
        ) {
            statement.setString(1, id);
            SmallFile smallFile = new SmallFile();
            try (ResultSet resultSet = statement.executeQuery()) {
                if (resultSet.next()) {
                    smallFile = this.wrapperSmallFile(resultSet);
                }
            }
            if (null != smallFile.getId()) {
                this.downloadFile(smallFile);
                return smallFile;
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    @Override
    public List<SmallFile> query(Page page) {
        List<SmallFile> smallFiles = new ArrayList<>();
        String sql = "select id, name, content_type, size, upload_date, md5 from file_info limit ? offset ?;";
        try (Connection connection = dataSource.getConnection();
             PreparedStatement statement = connection.prepareStatement(sql)
        ) {
            statement.setInt(1, page.getPageSize());
            statement.setInt(2, page.getPageIndex());
            try (ResultSet resultSet = statement.executeQuery()) {
                while (resultSet.next()) {
                    SmallFile smallFile = this.wrapperSmallFile(resultSet);
                    smallFiles.add(smallFile);
                }
            }
            smallFiles.parallelStream().forEach(this::downloadFile);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return smallFiles;
    }
    
    private void downloadFile(SmallFile smallFile) {
        OSSObject ossObject = ossClient.getObject(bucketName, smallFile.getId());
        try (InputStream in = ossObject.getObjectContent();
             ByteArrayOutputStream stream = new ByteArrayOutputStream();
             ReadableByteChannel readableByteChannel = Channels.newChannel(in);
             WritableByteChannel writableByteChannel = Channels.newChannel(stream)) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            while (readableByteChannel.read(byteBuffer) != -1) {
                byteBuffer.flip();
                if (byteBuffer.hasRemaining()) {
                    writableByteChannel.write(byteBuffer);
                }
                byteBuffer.clear();
            }
            byte[] bytes = stream.toByteArray();
            smallFile.setContent(new Binary(bytes));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private SmallFile wrapperSmallFile(ResultSet resultSet) throws SQLException {
        SmallFile smallFile = new SmallFile();
        smallFile.setId(resultSet.getString("id"));
        smallFile.setName(resultSet.getString("name"));
        smallFile.setContentType(resultSet.getString("content_type"));
        smallFile.setSize(resultSet.getLong("size"));
        smallFile.setUploadDate(resultSet.getTimestamp("upload_date").toLocalDateTime());
        smallFile.setMd5(resultSet.getString("md5"));
        return smallFile;
    }
}
